package card

import (
	"sort"
	"zinx_websokect_poker/zinx_app/poker_game/protocol"
)

//牌型大小：
//1、皇家同花顺＞同花顺＞四条＞葫芦＞同花＞顺子＞三条＞两队＞一对＞单牌
//2、牌点从大到小为：A、K、Q、J、10、9、8、7、6、5、4、3、2，各花色不分大小。
//3、同种牌型，对子时比对子的大小，其它牌型比最大的牌张，如最大牌张相同则比第二大的牌张，以此类推，都相同时为相同。
//4、A 可以组顺子 A、1、2、3、4、5  也可以组顺子 10、J、Q、K、A  同花顺同理
//所有牌型如下：
const (
	ROYAL_FLUSH    uint8= 10  // 皇家同花顺：同一花色的最大顺子。（最大牌：A-K-Q-J-10）
	STRAIGHT_FLUSH uint8= 9  // 同花顺：同一花色的顺子。（最大牌：K-Q-J-10-9 最小牌：A-2-3-4-5）
	FOUR           uint8= 8 // 四条：四同张加单张。（最大牌：A-A-A-A-K 最小牌：2-2-2-2-3）
	FULL_HOUSE     uint8= 7  // 葫芦（豪斯）：三同张加对子。（最大牌：A-A-A-K-K 最小牌：2-2-2-3-3）
	FLUSH          uint8= 6  // 同花：同一花色。（最大牌：A-K-Q-J-9 最小牌：2-3-4-5-7）
	STRAIGHT       uint8= 5 // 顺子：花色不一样的顺子。（最大牌：A-K-Q-J-10 最小牌：A-2-3-4-5）
	THREE          uint8= 4  // 三条：三同张加两单张。（最大牌：A-A-A-K-Q 最小牌：2-2-2-3-4）
	TWO_PAIR       uint8= 3  // 两对：（最大牌：A-A-K-K-Q 最小牌：2-2-3-3-4）
	ONE_PAIR       uint8= 2  // 一对：（最大牌：A-A-K-Q-J 最小牌：2-2-3-4-5）
	HIGH_CARD      uint8= 1 // 高牌：（最大牌：A-K-Q-J-9 最小牌：2-3-4-5-7）
)

//顺子（Straight，亦称“蛇”）
//此牌由五张顺序扑克牌组成。
//平手牌：如果不止一人抓到此牌，则五张牌中点数最大的赢得此局，
// 如果所有牌点数都相同，平分彩池。
func Straight(cards []int) (uint8, protocol.Straight){
	l := len(cards)

	if l < 5 {
		return 0, protocol.Straight{Point: cards[0]}
	}
	//顺子，差项为1的有穷等差数列，特殊项为1,2,3,4,5
	//cards 共5项 10 11 12 13 14  10-A
	//等差中项*2等于 a3 * 2=（a1+a5）

	sl1 := cards[4] - cards[3]
	sl2 := cards[3] - cards[2]
	sl3 := cards[2] - cards[1]
	sl4 := cards[1] - cards[0]
	dcx := cards[4] - cards[3]

	if sl2 == sl1 && sl3 == sl2 && sl3 == sl4 && 1 == dcx {
		return STRAIGHT, protocol.Straight{Point: cards[0]}
	}
	//特殊项为1,2,3,4,5；值为 1，2，3，4，13
	b := []int{1,2,3,4,13}
	if IntSliceEqual(cards, b){
		//最小的顺子
		return STRAIGHT, protocol.Straight{Point: 0}
	}

	return 0, protocol.Straight{Point: cards[0]}
}

//同花顺（Straight Flush）
//五张同花色的连续牌。
//平手牌：如果摊牌时有两副或多副同花顺，连续牌的头张牌大的获得筹码。
//如果是两副或多副相同的连续牌，平分筹码。
func StraightFlush(cards []int, flowers []int) (uint8, protocol.Straight){
	//调用同花和顺子的判断，两个都为真则返回同花顺
	flag,_ := Straight(cards)
	flag2,_ := Flush(flowers, cards)
	if flag == STRAIGHT && flag2 == FLUSH{
		return STRAIGHT_FLUSH, protocol.Straight{Point: cards[0]}
	}
	return 0, protocol.Straight{Point: cards[0]}
}

//皇家同花顺（Royal Flush）
//同花色的A, K, Q, J和10。
//平手牌：在摊牌的时候有两副多副皇家同花顺时，平分筹码。
func RoyalFlush(cards []int, flowers []int) uint8 {
	//满足同花顺后，再比对A, K, Q, J和10。
	flag,_ := StraightFlush(cards, flowers)
	if STRAIGHT_FLUSH == flag{
		b := []int{9,10,11,12,13}
		//比对点数是否符合
		if IntSliceEqual(cards, b){
			return ROYAL_FLUSH
		}
	}
	return 0
}

//四条（Four of a Kind，亦称“铁支”、“四张”或“炸弹”）
//其中四张是相同点数但不同花的扑克牌，第五张是随意的一张牌。
//平手牌：如果两组或者更多组摊牌，则四张牌中的最大者赢局，如果一组人持有的四张牌是一样的，
//那么第五张牌最大者赢局（起脚牌,2张起手牌中小的那张就叫做起脚牌）。如果起脚牌也一样，平分彩池。
func Four(cards []int) (uint8,protocol.Four){

	m := IntSliceCount(cards)

	n := 0
	nn := 0
	//四张一样的牌，返回四条
	for i,v := range m{
		if v == 4{
			n = i

		}
		if v == 1 {
			nn = i
		}
	}
	if n != 0 {
		return FOUR, protocol.Four{Fpoint: n,Opoint: nn}
	}

	return 0, protocol.Four{}
}

//满堂彩（Fullhouse，葫芦，三带二）
//由三张相同点数及任何两张其他相同点数的扑克牌组成。
//平手牌：如果两组或者更多组摊牌，那么三张相同点数中较大者赢局。
//如果三张牌都一样，则两张牌中点数较大者赢局，如果所有的牌都一样，则平分彩池。
func FullHouse(cards []int) (uint8,protocol.FullHouse){
	m := IntSliceCount(cards)
	i := false //三条标志
	j := false //一对标志
	three := 0
	two := 0
	//当两个标志同时为真，返回葫芦。
	for k,v := range m{
		if v == 3{
			i = true
			three = m[k]
		}
		if v == 2{
			j = true
			two = m[k]
		}
	}

	if i && j {
		return FULL_HOUSE, protocol.FullHouse{ThreePoint: three, TwoPoint: two}
	}

	return 0, protocol.FullHouse{}
}

//同花（Flush，简称“花”）
//此牌由五张不按顺序但相同花的扑克牌组成。
//平手牌：如果不止一人抓到此牌相，则牌点最高的人赢得该局，
//如果最大点相同，则由第二、第三、第四或者第五张牌来决定胜负，如果所有的牌都相同，平分彩池。
func Flush(flowers []int, cards []int) (uint8, protocol.Flush){
	l := len(flowers)

	if l < 5 {
		return 0, protocol.Flush{Cards: cards}
	}
	//同花标志
	flag := true

	for i := 0; i < l-1; i++ {
		if flowers[i] != flowers[i+1] {
			flag = false //有不同花色设为false。
		}
	}

	//标志为同花则返回同花牌型号
	if flag {
		return FLUSH, protocol.Flush{Cards: cards}
	}

	return 0, protocol.Flush{Cards: cards}
}

//三条（Three of a kind，亦称“三张”）
//由三张相同点数和两张不同点数的扑克组成。
//平手牌：如果不止一人抓到此牌，则三张牌中最大点数者赢局，
//如果三张牌都相同，比较第四张牌，必要时比较第五张，点数大的人赢局。如果所有牌都相同，则平分彩池。
func three(cards []int) (uint8, protocol.Three){
	m := IntSliceCount(cards)
	three := 0
	var point []int
	for i,v := range m{
		if v == 3{
			three = i
		}
		if v == 1 {
			point = append(point, i)
		}
	}
	if three != 0 {
		if point[1] > point[0] {
			return THREE, protocol.Three{ThreePoint: three, BigPoint: point[1], SmallPoint: point[0]}
		}else {
			return THREE, protocol.Three{ThreePoint: three, BigPoint: point[0], SmallPoint: point[1]}
		}
	}



	return 0, protocol.Three{}
}

//两对（Two Pairs）
//两对点数相同但两两不同的扑克和随意的一张牌组成。
//平手牌：如果不止一人抓大此牌相，牌点比较大的人赢，如果比较大的牌点相同，那么较小牌点中的较大者赢，
//如果两对牌点相同，那么第五张牌点较大者赢（起脚牌,2张起手牌中小的那张就叫做起脚牌）。如果起脚牌也相同，则平分彩池。
func TwoPair(cards []int)(uint8,protocol.TwoPair){
	m := IntSliceCount(cards)
	flag := 0
	var pair []int
	var singe int
	for i,v := range m{
		if v == 2{
			pair = append(pair, i)
			flag ++
		}else {
			singe = i
		}
	}

	if flag == 2 {
		max := pair[0]
		min := pair[1]
		if max < min {
			max,min = min,max
		}
		return TWO_PAIR, protocol.TwoPair{BigPoint: max, SmallPoint: min, SingePoint: singe}
	}

	return 0, protocol.TwoPair{}
}

//一对（One Pair）
//由两张相同点数的扑克牌和另三张随意的牌组成。
//平手牌：如果不止一人抓到此牌，则两张牌中点数大的赢，如果对牌都一样，则比较另外三张牌中大的赢，
//如果另外三张牌中较大的也一样则比较第二大的和第三大的，如果所有的牌都一样，则平分彩池。
func OnePair(cards []int) (uint8, protocol.OnePair){
	m := IntSliceCount(cards)
	pairPoint := 0
	var point []int
	for i,v := range m{
		if v == 2{
			pairPoint = i
		}else {
			point = append(point, i)
		}
	}
	//从小到大排序
	sort.Ints(point)
	if pairPoint != 0 {
		return ONE_PAIR, protocol.OnePair{PairPoint: pairPoint, Big: point[2], Middle:  point[1],Small:  point[0]}
	}

	return 0, protocol.OnePair{}
}


//判断两个Int切片是否相同
func IntSliceEqual(a, b []int) bool {
	if len(a) != len(b) {
		return false
	}

	if (a == nil) != (b == nil) {
		return false
	}

	for i, v := range a {
		if v != b[i] {
			return false
		}
	}

	return true
}

//牌型是否相同
func IntTypeEqual(a, b uint8) bool{

	if a != b  {
		return false
	}

	return true
}

//统计数组元素出现次数，返回map。
func IntSliceCount(cards []int)map[int]int{
	m := make(map[int]int)
	//把数组中元素出现的次数放进map，键对应数组元素，值对应出现次数。
	for _,v := range cards{
		if _,ok := m[v];!ok{
			m[v] = 1
		}else if m[v] > 0{
			m[v] += 1
		}
	}
	return m
}